using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Castle.Core.Internal;
using Framework.Core.Collections;
using Framework.Core.Io;
using Framework.Core.Process.Tasks;
using log4net;

namespace Framework.Core.Process
{
    public sealed class ProcessController : IProcessController
    {
        private readonly IProcessRunner processRunner;
        private readonly IIoAdapter ioAdapter;
        private readonly IAssemblyAdapter assemblyAdapter;

        private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessController));

        public ProcessController()
            : this(new ProcessRunner(), new IoAdapter(), new AssemblyAdapter())
        {
        }

        internal ProcessController(IProcessRunner processRunner)
            : this(processRunner, new IoAdapter(), new AssemblyAdapter())
        {

        }

        internal ProcessController(IProcessRunner processRunner, IIoAdapter ioAdapter, IAssemblyAdapter assemblyAdapter)
        {
            this.processRunner = processRunner;
            this.ioAdapter = ioAdapter;
            this.assemblyAdapter = assemblyAdapter;
        }

        public IEnumerable<IProcess> KillLocker(string path)
        {
            var result = new ConcurrentList<IProcess>();

            var directory = path;

            if (ioAdapter.FileExists(path))
            {
                directory = ioAdapter.GetFileDirectory(path);
            }

            if (!ioAdapter.DirectoryExists(directory))
            {
                Logger.ErrorFormat("Directory {0} not found, skipping kill", directory);
                return result;
            }

            if (!directory.EndsWith(@"\"))
            {
                directory += @"\";
            }

            var processesHandle = new Handle(directory).Execute(processRunner).ToList();

            Parallel.ForEach(processesHandle, Kill);

            return processesHandle;
        }

        public void Kill(IProcess process)
        {
            try
            {
                Logger.InfoFormat("Killing process {0} with id {1}", process.Name, process.ProcessId);
                processRunner.Start(new TaskKill(process.ProcessId));
            }
            catch (ProcessResultException)
            {
                Logger.InfoFormat("Process {0} not exists, skipping kill", process.ProcessId);
            }
        }

        public void Install(IWindowsServiceArgs windowsServiceArgs)
        {
            Uninstall(windowsServiceArgs);
            CreateService(windowsServiceArgs);
            Start(windowsServiceArgs);
        }

        private void Start(IWindowsServiceArgs windowsServiceArgs)
        {
            Logger.InfoFormat("Starting Service {0}", windowsServiceArgs);
            processRunner.Start(new Sc.ScStart(windowsServiceArgs.Name));
        }

        private void CreateService(IWindowsServiceArgs windowsServiceArgs)
        {
            Logger.InfoFormat("Creating service {0}", windowsServiceArgs);
            processRunner.Start(new Sc.ScCreate(windowsServiceArgs.Name,
                windowsServiceArgs.DisplayName,
                assemblyAdapter.GetEntryAssembly().Location,
                windowsServiceArgs.Args,
                windowsServiceArgs.User,
                windowsServiceArgs.Password));
        }

        public void Uninstall(IWindowsServiceArgs windowsServiceArgs)
        {
            KillMmc();
            StopService(windowsServiceArgs);
            DeleteService(windowsServiceArgs);
        }

        private void DeleteService(IWindowsServiceArgs windowsServiceArgs)
        {
            Logger.InfoFormat("Deleting Service {0}", windowsServiceArgs);
            processRunner.Start(new Sc.ScDelete(windowsServiceArgs.Name));
        }

        private void StopService(IWindowsServiceArgs windowsServiceArgs)
        {
            GetService(windowsServiceArgs).ForEach(s => SafeStop(windowsServiceArgs));
        }

        private void SafeStop(IWindowsServiceArgs windowsServiceArgs)
        {
            const int timeout = 20000;
            if (processRunner.Start(new NetStop(windowsServiceArgs.Name), timeout).IsTimeout)
            {
                Logger.WarnFormat("Stop Service {0} timeout({1}s)", windowsServiceArgs.Name, timeout);
                KillService(windowsServiceArgs);
            }
        }

        private void KillService(IWindowsServiceArgs windowsServiceArgs)
        {
            Logger.WarnFormat("Killing Service {0}", windowsServiceArgs.Name);
            GetService(windowsServiceArgs).ForEach(Kill);
        }

        private IEnumerable<WindowsService> GetService(IWindowsServiceArgs windowsServiceArgs)
        {
            var windowsServices = GetServices();
            return windowsServices.Where(s => HasSameName(windowsServiceArgs.Name, s.Name));
        }

        private void KillMmc()
        {
            const string mmc = "mmc";
            Logger.InfoFormat("Killing {0} to avoid conflict with service management", mmc);
            GetProcesses().ToList().Where(p => HasSameName(p.Name, mmc)).ForEach(Kill);
        }

        private static bool HasSameName(string one, string other)
        {
            return string.Equals(one, other, StringComparison.InvariantCultureIgnoreCase);
        }

        public IEnumerable<WindowsService> GetServices()
        {
            return new TaskList().Execute(processRunner).OfType<WindowsService>();
        }

        public IEnumerable<IProcess> GetProcesses()
        {
            return new TaskList().Execute(processRunner);
        }

        public ProcessResult Start(ICommand command, int timeout = -1)
        {
            var windowsService = command as WindowsService;
            
            if (windowsService != null)
            {
                return processRunner.Start(new Sc.ScStart(windowsService.Name), timeout);
            }
            
            return processRunner.Start(command, timeout);
        }
    }
}